package cn.cubix.flow.controller.workflow;

import cn.cubix.flow.controller.dto.workflow.DefinitionDto;
import cn.cubix.flow.controller.dto.workflow.DeploymentDto;
import cn.cubix.flow.util.Result;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.form.FormData;
import org.flowable.engine.form.FormProperty;
import org.flowable.engine.impl.RuntimeServiceImpl;
import org.flowable.engine.impl.cmd.StartProcessInstanceCmd;
import org.flowable.engine.impl.runtime.ProcessInstanceBuilderImpl;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/api/workflow/definition")
@RequiredArgsConstructor(onConstructor=@__({@Autowired,@Lazy}))
public class DefinitionApi {

    private final ProcessEngine processEngine;
    private final RuntimeServiceImpl runtimeService;

    @Setter(onMethod=@__(@Autowired),onParam = @__(@Lazy))
    private RepositoryService repositoryService;

    /** 分页查询流程定义 */
    @PostMapping("/page")
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_PAGE')")
    public Object page(@RequestBody JSONObject body){
        long current = 1L, size = 10L, total = 0L, start = 0L;
        if(body.containsKey("page")){
            JSONObject page = body.getJSONObject("page");
            current = page.getLong("current",current);
            size = page.getLong("size",size);
            start = (current-1L)*size;
        }
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        query.latestVersion();
        List<ProcessDefinition> processDefinitionList = query.listPage((int)start,(int)size);
        List<DefinitionDto> records = processDefinitionList.stream().map(DefinitionDto::from).collect(Collectors.toList());
        return Result.SUCCESS(records,"查询成功").setPage(current,size,query.count());
    }

    /** 部署流程定义 */
    @PostMapping(value = "/deploy",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_DEPLOY')")
    public Object deploy(
            @RequestParam(value = "filename",required = false) String filename,
            @RequestParam(value = "content",required = false) String content,
            @RequestParam(value = "file",required = false) MultipartFile file
    ){
        Result<Object> result = Result.FAILURE().setMessage("部署失败");

        if(StringUtils.isEmpty(content) && file!=null && "text/xml".equals(file.getContentType())){
            try{
                content = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8);
                filename = file.getOriginalFilename();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if(!StringUtils.isEmpty(content)){
            Document document = XmlUtil.readXML(content);
            Element _definitions = document.getDocumentElement();
            Element _process = XmlUtil.getElement(_definitions,"process");
            String key = _process.getAttribute("id");
            String category = _process.getAttribute("xsi:category");
            String parentDeploymentId = _process.getAttribute("xsi:parentDeploymentId");
            String tenantId = null;// TODO

            DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
                    .key(key)
                    .category(StringUtils.isEmpty(category)?null:category)
                    .parentDeploymentId(StringUtils.isEmpty(parentDeploymentId)?null:parentDeploymentId)
                    .addString(filename,content)
                    .name("接口上传部署："+filename)
                    .tenantId(StringUtils.isEmpty(tenantId)?null:tenantId);
            Deployment deployment = deploymentBuilder.deploy();
            result.toSuccess().setMessage("部署成功");
        }

        return result;
    }

    /** 获取表单字段 */
    @GetMapping(value = "/fields/{definitionId}")
    public Object fields(@PathVariable("definitionId")String definitionId){
        Result<Object> result = Result.FAILURE().setMessage("查询失败");

        JSONArray fields = new JSONArray();
        FormData formData = runtimeService.getFormInstanceById(definitionId);
        List<FormProperty> formProperties = formData.getFormProperties();
        if(formProperties!=null){
            formProperties.forEach(formProperty -> {
                fields.add(new JSONObject()
                        .set("id",formProperty.getId())
                        .set("name",formProperty.getName())
                        .set("type",formProperty.getType().getName())
                        .set("value",formProperty.getValue())
                        .set("readable",formProperty.isReadable())
                        .set("writable",formProperty.isWritable())
                        .set("required",formProperty.isRequired())
                );
            });
        }
        result.toSuccess().setMessage("查询成功").setRecord(fields);
        return result;
    }

    /** 发起流程 */
    @PostMapping(value = "/start",consumes = {MediaType.APPLICATION_JSON_VALUE})
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_START')")
    public Object start(@RequestBody JSONObject body){
        Result<Object> result = Result.FAILURE().setMessage("发起流程失败");

        if(body.containsKey("params")) {
            JSONObject params = body.getJSONObject("params");
            String definitionId = params.getStr("definitionId");
            String processInstanceName = params.getStr("processInstanceName");
            String businessKey = params.getStr("businessKey");
            String startUserId = params.getStr("startUserId");
            String tenantId = params.getStr("tenantId");// TODO
            Map<String,Object> variables = params.getJSONObject("variables");

            Authentication.setAuthenticatedUserId(startUserId);

            ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder()
                    .processDefinitionId(definitionId)
                    .name(processInstanceName)
                    .businessKey(businessKey)
                    .variables(variables)
                    .tenantId(tenantId);
            ProcessInstance processInstance = runtimeService.getCommandExecutor()
                    .execute(new StartProcessInstanceCmd<ProcessInstance>((ProcessInstanceBuilderImpl)processInstanceBuilder));

            Authentication.setAuthenticatedUserId(null);

            result.toSuccess().setMessage("发起流程成功");
        }
        return result;
    }

    /** 挂起流程定义 */
    @PostMapping(value = "/suspend",consumes = {MediaType.APPLICATION_JSON_VALUE})
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_SUSPEND')")
    public Object suspend(@RequestBody JSONObject body){
        Result<Object> result = Result.FAILURE("流程定义挂起失败");
        String definitionId = body.getStr("definitionId");
        if(!StrUtil.isEmpty(definitionId)) {
            repositoryService.suspendProcessDefinitionById(definitionId);
            result.toSuccess().setMessage("流程定义挂起成功");
        }
        return result;
    }

    /** 激活流程定义 */
    @PostMapping(value = "/activate",consumes = {MediaType.APPLICATION_JSON_VALUE})
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_ACTIVATE')")
    public Object activate(@RequestBody JSONObject body){
        Result<Object> result = Result.FAILURE("流程定义激活失败");
        String definitionId = body.getStr("definitionId");
        if(!StrUtil.isEmpty(definitionId)) {
            repositoryService.activateProcessDefinitionById(definitionId);
            result.toSuccess().setMessage("流程定义激活成功");
        }
        return result;
    }

    /** 获取部署的BPMN */
    @GetMapping(value="/deployment/{deploymentId}")
    @PreAuthorize("hasAnyAuthority('ADMIN','API_WORKFLOW_DEFINITION_DEPLOYMENT')")
    public Object getDeployment(@PathVariable("deploymentId")String deploymentId){
        Result<Object> result = Result.FAILURE("流程部署获取失败");

        Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
        if(deployment!=null){
            DeploymentDto deploymentDto = DeploymentDto.from(deployment);
            repositoryService.getDeploymentResourceNames(deploymentId).forEach(name->{
                if(name.toUpperCase().endsWith("XML")){
                    String bpmnXml = IoUtil.read(repositoryService.getResourceAsStream(deploymentId,name),StandardCharsets.UTF_8);
                    deploymentDto.setBpmnXml(bpmnXml);
                }
            });
            result.setRecord(deploymentDto);
            result.toSuccess().setMessage("流程部署获取成功");
        }
        return result;
    }
}
